/* Includes ------------------------------------------------------------------*/
#include "jsmn_parser.h"
#include "puresun_func.h"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"

/* Private macro -------------------------------------------------------------*/

/* Private typedef -----------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/

/* Private function prototypes -----------------------------------------------*/

// debug function, for test, you can delete it if you don't need it
void print_jk_list(jk_list_t *head);
void print_jk_tree(jk_list_t *head);

// internal function, for internal use
void free_jk_list(jk_list_t *head);
string get_json_token_type(js_vessel_t *js_vessel, int token_id);

json_err_t parse_json_path(string path, jk_list_t **out);

json_err_t get_token_id_by_array_index(js_vessel_t *js_vessel, int array_token_id, int array_index, int *out);
json_err_t get_token_id_by_jk_list(js_vessel_t *js_vessel, jk_list_t *jk_list, int *out);
json_err_t get_token_id_by_key(js_vessel_t *js_vessel, int object_token_id, string key, int *out);
json_err_t get_token_id_by_path(js_vessel_t *js_vessel, string path, int *out);
/* Private functions ---------------------------------------------------------*/

/*******************************************************************************
 * Function Name : get_bool_by_path
 * Description   : get bool by path in json
 * Input         : [js_vessel_t *js_vessel, string path, bool *out, bool default_value]
 *                 *js_vessel: the json vessel
 *                 path: the path string
 *                 *out: the adress of output bool pointer
 *                 default_value: the default value if the path is not found
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                 JSON_ERR_KEY_NOT_PRIMITIVE: the key is not a primitive
 * Notes         : the path only can be target to a bool
 *******************************************************************************/
json_err_t get_bool_by_path(js_vessel_t *js_vessel, string path, bool *out, bool default_value)
{
    // set the out to default value
    *out = default_value;

    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK)
    {
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    int len = token.end - token.start;
    if (token.type != JSMN_PRIMITIVE)
    {
        return JSON_ERR_KEY_NOT_PRIMITIVE;
    }

    // copy the token content to buff
    char buff[6];
    memset(buff, 0, 6);
    strncpy(buff, js_vessel->json + token.start, pf_min(len, 5));
    if (strcmp(buff, "true") == 0)
    {
        *out = true;
    }
    else
    {
        *out = false;
    }

    // return
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_array_size_by_path
 * Description   : get array size by path in json
 * Input         : [js_vessel_t *js_vessel, string path, int *out]
 *                 js_vessel: the json vessel
 *                 path: the path string
 *                 *out: the adress of output array size
 * Return        : JSON_OK: success
 *                JSON_ERR: error of unknown
 *                JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                JSON_ERR_KEY_NOT_ARRAY: the key is not a array
 * Notes         : the path only can be target to a array, if the path is not a array,
 *               it will return the JSON_ERR_KEY_NOT_ARRAY error and the out will be
 *               set to -1
 *******************************************************************************/
json_err_t get_array_size_by_path(js_vessel_t *js_vessel, string path, int *out)
{
    // set the out to -1
    *out = -1;

    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK)
    {
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    if (token.type != JSMN_ARRAY)
    {
        return JSON_ERR_KEY_NOT_ARRAY;
    }

    // set the out to the array size
    *out = token.size;
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_string_by_path_alloc
 * Description   : get string by path in json and allocate the memory
 * Input         : [js_vessel_t *js_vessel, string path, string *out, string default_value, int max_len]
 *                 *js_vessel: the json vessel
 *                 path: the path string, eg: "key1.key2.key3[4].key4"
 *                 *out: the adress of output string pointer
 *                 default_value: the default value if the path is not found
 *                 max_len: the max length of the output string
 *                          - >0 : the max length of the output string
 *                          - 0  : the output string length is not limited, you
 *                                need release the memory after use
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                 JSON_ERR_MAX_LEN_IS_NEGATIVE: the max_len is negative
 *                 JSON_ERR_OBJECT_TOO_LONG: the object is too long
 *                 JSON_ERR_KEY_NOT_STRING: the key is not a string
 * Notes         : the path only can be target to a string, if the path is not a
 *               string, it will return the JSON_ERR_KEY_NOT_STRING error and
 *               the out will be set to default_value.
 *                 in this function, the out will be allocated the memory in all
 *               case, at last 1 byte will be allocated
 *******************************************************************************/
json_err_t get_string_by_path_alloc(
    js_vessel_t *js_vessel,
    string path,
    string *out,
    string default_value,
    int max_len)
{
    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // check max_len
    if (max_len < 0)
    {
        return JSON_ERR_MAX_LEN_IS_NEGATIVE;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK || js_vessel->tokens[token_id].type != JSMN_STRING)
    {
        if (default_value == NULL)
        {
            *out = malloc(1);
            *out[0] = '\0';
        }
        else
        {
            int df_len = strlen(default_value);
            if (max_len == 0)
            {
                *out = malloc(df_len + 1);
                memcpy(*out, default_value, df_len);
                (*out)[df_len] = '\0';
            }
            else
            {
                *out = malloc(pf_min(df_len, max_len) + 1);
                strncpy(*out, default_value, pf_min(df_len, max_len));
                (*out)[pf_min(df_len, max_len)] = '\0';
            }
        }
        if (js_vessel->tokens[token_id].type != JSMN_STRING)
        {
            return JSON_ERR_KEY_NOT_STRING;
        }
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    int len = token.end - token.start;
    if (max_len == 0)
    {
        *out = malloc(len + 1);
        strncpy(*out, js_vessel->json + token.start, len);
        (*out)[len] = '\0';
    }
    else
    {
        *out = malloc(pf_min(len, max_len) + 1);
        strncpy(*out, js_vessel->json + token.start, pf_min(len, max_len));
        (*out)[pf_min(len, max_len)] = '\0';
        if (len > max_len)
        {
            return JSON_ERR_OBJECT_TOO_LONG;
        }
    }

    // return
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_string_by_path
 * Description   : get string by path in json
 * Input         : [js_vessel_t *js_vessel, string path, string out, string default_value, int max_len]
 *                 *js_vessel: the json vessel
 *                 path: the path string, eg: "key1.key2.key3[4].key4"
 *                 out: the adress of output string pointer
 *                 default_value: the default value if the path is not found
 *                 max_len: the max length of the output string
 *                          - >0 : the max length of the output string
 *                          - 0  : the output string length is not limited, you
 *                                need allocate the enough memory for the output
 * Return        : JSON_OK: success
 *                JSON_ERR: error of unknown
 *                JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                JSON_ERR_MAX_LEN_IS_NEGATIVE: the max_len is negative
 *                JSON_ERR_OBJECT_TOO_LONG: the object is too long
 *                JSON_ERR_KEY_NOT_STRING: the key is not a string
 * Notes         : the path only can be target to a string, if the path is not a string,
 *                it will return the JSON_ERR_KEY_NOT_STRING error and the out will be set to default_value
 *******************************************************************************/
json_err_t get_string_by_path(js_vessel_t *js_vessel, string path, string out, string default_value, int max_len)
{
    // set the out to default value
    out[0] = '\0';

    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // check max_len
    if (max_len < 0)
    {
        return JSON_ERR_MAX_LEN_IS_NEGATIVE;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK || js_vessel->tokens[token_id].type != JSMN_STRING)
    {
        if (default_value == NULL)
        {
            out[0] = '\0';
        }
        else
        {
            int df_len = strlen(default_value);
            if (max_len == 0)
            {
                memcpy(out, default_value, df_len);
                out[df_len] = '\0';
            }
            else
            {
                memcpy(out, default_value, pf_min(df_len, max_len));
                out[pf_min(df_len, max_len)] = '\0';
            }
        }
        if (js_vessel->tokens[token_id].type != JSMN_STRING)
        {
            return JSON_ERR_KEY_NOT_STRING;
        }
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    int len = token.end - token.start;
    if (max_len == 0)
    {
        strncpy(out, js_vessel->json + token.start, len);
        out[len] = '\0';
    }
    else
    {
        strncpy(out, js_vessel->json + token.start, pf_min(len, max_len));
        out[pf_min(len, max_len)] = '\0';
        if (len > max_len)
        {
            return JSON_ERR_OBJECT_TOO_LONG;
        }
    }

    // return
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_number_by_path
 * Description   : get number by path in json
 * Input         : [js_vessel_t *js_vessel, string path, int *out, int default_value]
 *                 *js_vessel: the json vessel
 *                 path: the path string
 *                 *out: the adress of output number pointer
 *                 default_value: the default value if the path is not found
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                 JSON_ERR_KEY_NOT_NUMBER: the key is not a number
 * Notes         : the path only can be target to a number, if the path is not a number,
 *                 it will return the JSON_ERR_KEY_NOT_NUMBER error and the out will be set to default_value
 *******************************************************************************/
json_err_t get_number_by_path(js_vessel_t *js_vessel, string path, int *out, int default_value)
{
    // set the out to default value
    *out = default_value;

    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK)
    {
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    int len = token.end - token.start;
    if (len > 9 || len < 1 || token.type != JSMN_PRIMITIVE)
    {
        return JSON_ERR_KEY_NOT_NUMBER;
    }

    // copy the token content to buff
    char buff[10];
    memset(buff, 0, 10);
    strncpy(buff, js_vessel->json + token.start, len);

    // check if the buff is a number
    if (!pf_str_is_number(buff))
    {
        return JSON_ERR_KEY_NOT_NUMBER;
    }

    // set the out to the number
    *out = atoi(buff);
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_content_by_path
 * Description   : get content string by path in json
 * Input         : [js_vessel_t *js_vessel, string path, string *out, string default_value, int max_len]
 *                 *js_vessel: the json vessel
 *                 path: the path string, eg: "key1.key2.key3[4].key4"
 *                 *out: the adress of output string pointer
 *                 default_value: the default value if the path is not found
 *                 max_len: the max length of the output string
 *                          - >0 : the max length of the output string
 *
 *                          - 0  : the output string length is not limited, you
 *                                 need allocate the enough memory for the output
 *
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 *                 JSON_ERR_MAX_LEN_IS_NEGATIVE: the max_len is negative
 *                 JSON_ERR_OBJECT_TOO_LONG: the object is too long
 * Notes         : the path can be target to a object, array, string, bool, int, float or null
 *                 the path can be a high level array, eg: array[4][2][1]
 *                 the path can be a high level object, eg: object1.object2.object3
 *                 the path can be a high level object and array, eg: object1.object2.object3[4][2][1]
 *                 the path can be a high level array and object, eg: array[4][2][1].object1.object2.object3
 *******************************************************************************/
json_err_t get_content_by_path(js_vessel_t *js_vessel, string path, string out, string default_value, int max_len)
{
    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // check max_len
    if (max_len < 0)
    {
        return JSON_ERR_MAX_LEN_IS_NEGATIVE;
    }

    int token_id = 0;
    err = get_token_id_by_path(js_vessel, path, &token_id);
    if (err != JSON_OK)
    {
        if (default_value == NULL)
        {
            out[0] = '\0';
        }
        else
        {
            int df_len = strlen(default_value);
            if (max_len == 0)
            {
                memcpy(out, default_value, df_len);
                out[df_len] = '\0';
            }
            else
            {
                memcpy(out, default_value, pf_min(df_len, max_len));
                out[pf_min(df_len, max_len)] = '\0';
            }
        }
        return err;
    }

    // get the token content
    jsmntok_t token = js_vessel->tokens[token_id];
    int len = token.end - token.start;
    if (max_len == 0)
    {
        strncpy(out, js_vessel->json + token.start, len);
        out[len] = '\0';
    }
    else
    {
        strncpy(out, js_vessel->json + token.start, pf_min(len, max_len));
        out[pf_min(len, max_len)] = '\0';
        if (len > max_len)
        {
            return JSON_ERR_OBJECT_TOO_LONG;
        }
    }

    // return
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_token_id_by_path
 * Description   : get token id by path in tokens array
 * Input         : [js_vessel_t *js_vessel, string path, int *out]
 *                 js_vessel: the json vessel
 *                 path: the path string
 *                 *out: the adress of output token id
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 * Notes         : the path can be target to a object, array, string, bool, int, float or null
 *******************************************************************************/
json_err_t get_token_id_by_path(js_vessel_t *js_vessel, string path, int *out)
{
    // check parameters
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check path
    if (path == NULL)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // parse the path
    jk_list_t *jk_list = NULL;
    err = parse_json_path(path, &jk_list);
    if (err != JSON_OK)
    {
        free_jk_list(jk_list);
        return err;
    }

    // get the token id by jk_list
    err = get_token_id_by_jk_list(js_vessel, jk_list, out);

    // free the jk_list
    free_jk_list(jk_list);

    // return
    return err;
}

/*******************************************************************************
 * Function Name : parse_json_path
 * Description   : parse json path
 * Input         : [string path, jk_list_t **out]
 *                 path: the path string, eg: "key1.key2.key3[4].key4"
 *                 *out: the adress of head of output list pointer
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 * Notes         : the path can be target to a object, array, string, bool, int, float or null
 *******************************************************************************/
json_err_t parse_json_path(string path, jk_list_t **out)
{
    // check parameters
    // check if the key path str is valid
    if (path == NULL || strlen(path) == 0)
    {
        return JSON_ERR_KEY_PATH_IS_NULL;
    }

    // check if the out pointer is valid
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // parse the key path
    jk_list_t *current;
    string key = path;
    string key_start = key;

    // set the head data node to null
    *out = malloc(sizeof(jk_list_t));
    memset(*out, 0, sizeof(jk_list_t));
    current = *out;

    while (b_true)
    {
        // key++;
        if (*key == '.' || *key == '[' || *key == '\0')
        {
            // key detected, malloc a new node memory
            current->next = malloc(sizeof(jk_list_t));

            // move to the next node
            current = current->next;

            // malloc the key string memory
            current->key = malloc(key - key_start + 1);

            // copy the key string to the node
            strncpy(current->key, key_start, key - key_start);

            // set the end of the key string to null
            current->key[key - key_start] = '\0';

            // set the array_index to -1, mark this node as not an array defautly
            current->array_index = -1;

            // move the current pointer.next to null
            current->next = NULL;

            // if the key is not end, move the key_start pointer to the next char
            key_start = key + 1;
        }
        if (*key == '\0')
            break;
        if (*key == '[')
        {
            // array index detected, parse the array index and set the array_index to 0
            current->array_index = 0;

            // move the key pointer to the next char, it should be a number normally
            // parse the array index in a while loop
            while (*++key != ']')
            {
                // if the next char is not a number, return error
                if (!pf_char_is_num(*key))
                {
                    // reset the array_index to -1, mark it as invalid
                    current->array_index = -1;

                    // free the list
                    free_jk_list(*out);

                    // return error
                    return JSON_ERR_KEY_PATH_INVALID_ARRAY_INDEX;
                }
                current->array_index = current->array_index * 10 + (*key - '0');
            }

            key_start = ++key;
            if (*key == '.')
            {
                key_start = key + 1;
            }
        }

        // move to the next char if the next char is start of a key or array index
        if (*key != '[')
            key++;
    }

    return JSON_OK;
}

/*******************************************************************************
 * Function Name : parse_json_buffer
 * Description   : parse json string to jsmntok_t array
 * Input         : [js_vessel_t *js_vessel]
 *                 *js_vessel: the json vessel
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_VESSLE_IS_NULL: the json vessel is null
 *                 JSON_ERR_VESSLE_JSON_IS_NULL: the json string in the vessel is null
 *                 JSON_ERR_VESSLE_JSON_TOO_SHORT: the json string in the vessel is too short
 *                 JSON_ERR_VESSLE_JSON_TOO_LONG: the json string in the vessel is too long
 *                 JSON_ERR_JSON_STRING_FORMAT_ERROR: the json string format is error
 *                 JSON_ERR_JSON_STRING_NOT_COMPLETE: the json string is not complete
 * Notes         : none
 *******************************************************************************/
json_err_t parse_json_buffer(js_vessel_t *js_vessel)
{
    // check parameters
    if (js_vessel == NULL)
    {
        return JSON_ERR_VESSLE_IS_NULL;
    }

    // check if the json string in the vessel is valid
    if (js_vessel->json == NULL)
    {
        return JSON_ERR_VESSLE_JSON_IS_NULL;
    }

    // check if the json string in the vessel is too short
    if (strlen(js_vessel->json) < 2)
    {
        return JSON_ERR_VESSLE_JSON_IS_TOO_SHORT;
    }

    // check if the json string in the vessel is too long
    if (strlen(js_vessel->json) > MAX_JSON_SIZE)
    {
        return JSON_ERR_VESSLE_JSON_IS_TOO_LONG;
    }

    // parse the json string
    jsmn_parser parser;
    int tk_size = DEFAULT_JSON_TOKEN_SIZE;

    // reset the out pointer and out_count pointer
    js_vessel->tokens = NULL;
    js_vessel->tokens_count = 0;

    // try to parse the json string
    while (b_true)
    {
        js_vessel->tokens = malloc(sizeof(jsmntok_t) * tk_size);
        jsmn_init(&parser);
        js_vessel->tokens_count = jsmn_parse(&parser, js_vessel->json, strlen(js_vessel->json), js_vessel->tokens, tk_size);
        if (js_vessel->tokens_count == JSMN_ERROR_NOMEM)
        {
            // if the token count is not enough, realloc the token memory
            tk_size += DEFAULT_JSON_TOKEN_SIZE;
            // printf("tk_size is not enough, realloc to %d\n", tk_size);
            free(js_vessel->tokens);
            if (tk_size > MAX_JSON_TOKEN_SIZE)
            {
                return JSON_ERR_JSON_TOKENS_IS_TOO_LONG;
            }
            continue;
        }
        if (js_vessel->tokens_count == JSMN_ERROR_INVAL)
        {
            free(js_vessel->tokens);
            return JSON_ERR_JSON_STRING_FORMAT_ERROR;
        }
        if (js_vessel->tokens_count == JSMN_ERROR_PART)
        {
            free(js_vessel->tokens);
            return JSON_ERR_JSON_STRING_NOT_COMPLETE;
        }
        break;
    }

    // return ok
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_token_id_by_jk_list
 * Description   : get token id by jk_list
 * Input         : [js_vessel_t *js_vessel, jk_list_t *jk_list, int *out]
 *                 js_vessel: the json vessel
 *                 jk_list: the jk_list
 *                 *out: the adress of output token id
 * Return        : >=0: the token index of the path's token index
 *                 -1: path not found
 * Notes         : this function is only for internal use
 *******************************************************************************/
json_err_t get_token_id_by_jk_list(js_vessel_t *js_vessel, jk_list_t *jk_list, int *out)
{
    int target_token_id = 0;
    jk_list_t *current = jk_list->next;
    while (current)
    {
        if (strlen(current->key) > 0)
        {
            // target_token_id =
            json_err_t err = get_token_id_by_key(js_vessel, target_token_id, current->key, &target_token_id);
            if (err != JSON_OK)
            {
                return err;
            }

            // if the target_token_id is less than 0, it means the key is not found
            // if (target_token_id < 0)
            //     return -1;
        }
        if (current->array_index != -1)
        {
            json_err_t err = get_token_id_by_array_index(js_vessel, target_token_id, current->array_index, &target_token_id);
            if (err != JSON_OK)
            {
                return err;
            }
            // if the target_token_id of the array item is less than 0, it means the array is not found
            // if (target_token_id < 0)
            //     return -1;
        }
        current = current->next;
    }
    *out = target_token_id;
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : get_token_id_by_key
 * Description   : get token by object name
 * Input         : [js_vessel_t *js_vessel, int object_token_id, string key, int *out]
 *                 js_vessel: the json vessel
 *                 object_token_id: the object token id
 *                 key: the key name
 *                 *out: the adress of output token id
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_KEY_PATH_IS_NULL: the key path is null
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 *                 JSON_ERR_KEY_NOT_FOUND: the key is not found
 *
 * Notes         : the object can be a object, array, string, bool, int, float or null
 *******************************************************************************/
json_err_t get_token_id_by_key(js_vessel_t *js_vessel, int object_token_id, string key, int *out)
{
    // check json_vessel
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check object_token_id
    if (object_token_id >= js_vessel->tokens_count)
        return JSON_ERR_TOKENID_IS_OUT_OF_RANGE;

    if (js_vessel->tokens[object_token_id].type != JSMN_OBJECT)
        return JSON_ERR_TOKENID_IS_NOT_OBJECT;

    // check key
    if (key == NULL || strlen(key) == 0) // the key name should not be null
        return JSON_ERR_KEY_PATH_IS_NULL;

    // search the key in the tokens
    for (int i = object_token_id + 1; i < js_vessel->tokens_count; i++)
    {
        // if the token[i]'s parent is less than object_token_id, it means the token[i] is not in the object
        if (js_vessel->tokens[i].parent < object_token_id)
            return JSON_ERR_KEY_NOT_FOUND;

        // if the token[i]'s parent is equal to object_token_id, it means the token[i] is in the object
        if (js_vessel->tokens[i].parent == object_token_id)
        {
            // if the token[i]'s type is JSMN_STRING, it means the token[i] is a key
            // the token[i]'s size should be 1, it means the token[i] is a key
            if (js_vessel->tokens[i].type == JSMN_STRING && js_vessel->tokens[i].size == 1)
            {
                int len_content = js_vessel->tokens[i].end - js_vessel->tokens[i].start;
                int len_key = strlen(key);
                // if the token[i]'s content length is equal to the key length, it means the token[i] is maybe the key we want
                if (len_content == len_key)
                {
                    // if the token[i]'s content is equal to the key, it means the token[i] is the key we want
                    if (strncmp(key, js_vessel->json + js_vessel->tokens[i].start, len_key) == 0)
                    {
                        // return the token[i]'s value token index
                        *out = i + 1;
                        return JSON_OK;
                    }
                }
            }
        }
    }

    return JSON_ERR_KEY_NOT_FOUND;
}

/*******************************************************************************
 * Function Name : get_token_id_by_array_index
 * Description   : get token by array index
 * Input         : [js_vessel_t *js_vessel, int array_token_id, int array_index, int *out]
 *                 js_vessel: the json vessel
 *                 array_token: the array token start, should be a JSMN_ARRAY token id
 *                 array_index: the array index
 *                 *out: the adress of output token id
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_ARRAY_TOKEN_IS_NULL: the array token is null
 *                 JSON_ERR_ARRAY_TOKEN_IS_NOT_ARRAY: the array token is not a array
 *                 JSON_ERR_ARRAY_INDEX_IS_OUT_OF_RANGE: the array index is out of range
 *                 JSON_ERR_OUT_IS_NULL: the out pointer is null
 * Notes         : the array can be a object, array, string, bool, int, float or null
 *******************************************************************************/
json_err_t get_token_id_by_array_index(js_vessel_t *js_vessel, int array_token_id, int array_index, int *out)
{
    int id = 0;

    // check js_vessel
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return err;
    }

    // check array_token
    if (array_token_id >= js_vessel->tokens_count || array_token_id < 0)
        return JSON_ERR_ARRAY_TOKEN_IS_OUT_OF_RANGE;
    if (js_vessel->tokens[array_token_id].type != JSMN_ARRAY)
        return JSON_ERR_ARRAY_TOKEN_IS_NOT_ARRAY;

    // check array_index
    if (array_index < 0 || array_index >= js_vessel->tokens[array_token_id].size)
        return JSON_ERR_ARRAY_INDEX_IS_OUT_OF_RANGE;

    // check out
    *out = -1;

    // search the key in the tokens
    for (int i = array_token_id + 1; i < js_vessel->tokens_count; i++)
    {
        if (js_vessel->tokens[i].parent == array_token_id)
        {
            if (id == array_index)
            {
                *out = i;
                return JSON_OK;
            }
            id++;
        }

        // if the token[i]'s parent is less than array_token, it means the token[i] is not in the array
        if (js_vessel->tokens[i].parent < array_token_id)
            return JSON_ERR_ARRY_ITEM_NOT_FOUND;
    }
    return JSON_ERR_ARRY_ITEM_NOT_FOUND;
}

/*******************************************************************************
 * Function Name: read_json_file
 * Description  : read json file
 * Input        : [string path, byte **out, bool_t non_space]
 *                - path: the path of the json file
 *                - **out: the buffer to store the json file content
 *                - non_space: b_true, the space, tab, newline etc. will be removed
 *                             b_false, the space, tab, newline etc. will not be removed
 *                  if the json file is too large, it is recommended to set non_space
 *                to b_true to save memory and improve the parsing speed
 *
 * Return Value : the json file read result
 *                - JSON_OK: success
 *                - JSON_ERR: unknown error
 *                - JSON_ERR_FILE_NOT_EXIST: the file is not exist
 *                - JSON_ERR_ALLOCATE_MEMORY_FAILED: allocate memory failed
 *                - JSON_ERR_JSON_PATH_IS_NULL: the path is null
 *                - JSON_ERR_OUT_IS_NULL: the out pointer is null
 * Note         : none
 *******************************************************************************/
json_err_t read_json_file(string path, byte **out, bool_t non_space)
{
    // check path
    if (path == NULL || strlen(path) == 0)
    {
        return JSON_ERR_JSON_PATH_IS_NULL;
    }

    // check out
    if (out == NULL)
    {
        return JSON_ERR_OUT_IS_NULL;
    }

    // Attention: to read json file correctly, we need to read it as binary
    FILE *fp = fopen(path, "rb");
    if (!fp)
    {
        return JSON_ERR_FILE_NOT_EXIST;
    }

    // get file size
    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    // read file
    byte *buff = (byte *)malloc(size + 1);

    if (!buff)
    {
        fclose(fp);
        return JSON_ERR_ALLOC_MEMORY_FAILED;
    }

    // read file
    fread(buff, 1, size, fp);
    fclose(fp);
    buff[size] = '\0';

    if (non_space)
    {
        // remove space, tab, newline
        int x = pf_count_char_without_space_newline(buff);
        byte *buff2 = (byte *)malloc(x + 1);
        if (!buff2)
        {
            free(buff);
            return JSON_ERR_ALLOC_MEMORY_FAILED;
        }

        // remove space, tab, newline etc.
        // to make json parser work faster and save memory
        pf_remove_space_tab_newline(buff, buff2);
        free(buff);
        buff = buff2;
    }
    // return buff;
    *out = buff;
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : free_jk_list
 * Description   : free the jk_list
 * Input         : [jk_list_t *head]
 *                 head: the head of the jk_list
 * Return        : none
 * Notes         : none
 *******************************************************************************/
void free_jk_list(jk_list_t *head)
{
    // recyle the list memory from head to tail
    while (b_true)
    {
        // check if the head is null
        if (head == NULL)
            break;

        // record the next node
        jk_list_t *next = head->next;

        // free the current node key string if it is not null
        if (head->key)
            free(head->key);

        // free the current node
        free(head);

        // move to the next node
        head = next;
    }
}

/*******************************************************************************
 * Function Name : print_jk_list
 * Description   : print the jk_list
 * Input         : [jk_list_t *head]
 *                 head: the head of the jk_list
 * Return        : NULL
 * Notes         : print the jk_list from head to tail
 *******************************************************************************/
void print_jk_list(jk_list_t *head)
{
    // print the list from head to tail
    head = head->next;
    while (head)
    {
        printf("key: %s, array_index: %d\n", head->key, head->array_index);
        head = head->next;
    }
}

/*******************************************************************************
 * Function Name : print_jk_tree
 * Description   : print the jk_list tree
 * Input         : [jk_list_t *head]
 *                 head: the head of the jk_list
 * Return        : void
 * Notes         : print the jk_list tree
 *******************************************************************************/
void print_jk_tree(jk_list_t *head)
{
    // print the list from head to tail
    head = head->next;
    while (head)
    {
        printf("%s", head->key);
        if (head->array_index != -1)
            printf("[%d]", head->array_index);
        if (head->next)
        {
            if (*head->next->key)
                printf(".");
        }
        head = head->next;
    }
}

/*******************************************************************************
 * Function Name : get_json_token_type
 * Description   : get the json token type
 * Input         : [js_vessel_t *js_vessel, int token_id]
 *                 - js_vessel: target js_vessel
 *                 - token_id: the token id
 * Return        : the token type string pointer
 * Notes         : none
 *******************************************************************************/
string get_json_token_type(js_vessel_t *js_vessel, int token_id)
{
    // check if the js_vessel is null
    if (js_vessel == NULL || js_vessel->tokens == NULL || js_vessel->tokens_count == 0)
    {
        return "vessel_err";
    }

    // check if the token id is valid
    if (token_id < 0 || token_id >= js_vessel->tokens_count)
    {
        return "token_err ";
    }

    // match the token type
    switch (js_vessel->tokens[token_id].type)
    {
    case JSMN_STRING:
        if (js_vessel->tokens[token_id].size == 1)
            return "    key   ";
        return "  string  ";
        // break;
    case JSMN_PRIMITIVE:
        return "primitive ";
        // break;
    case JSMN_OBJECT:
        return "  object  ";
        // break;
    case JSMN_ARRAY:
        return "  array   ";
        // break;
    default:
        return " unknown  ";
        // break;
    }
}

/*******************************************************************************
 * Function Name : print_token_list
 * Description   : print the token list
 * Input         : [js_vessel_t *js_vessel]
 *                 - js_vessel: target js_vessel
 * Return        : NULL
 * Notes         : print the token list from head to tail, the line will be cut
 *                to 32 bytes
 *******************************************************************************/
void print_token_list(js_vessel_t *js_vessel)
{
    // print the list from head to tail
    printf("token count: %d\n", js_vessel->tokens_count);
    printf("+-------+------------+------+--------+----------------------------------+\n");
    printf("| tk_id |    type    | size | parent |              value               |\n");
    printf("+-------+------------+------+--------+----------------------------------+\n");
    for (int i = 0; i < js_vessel->tokens_count; i++)
    {
        // match the token type
        string type = get_json_token_type(js_vessel, i);

        printf("| %-5d | %s | %-4d |  %-4d  | ", i, type, js_vessel->tokens[i].size, js_vessel->tokens[i].parent);
        if (js_vessel->tokens[i].end - js_vessel->tokens[i].start > 32)
        {
            printf("%.*s%s |\n", 32 - 3, js_vessel->json + js_vessel->tokens[i].start, "...");
        }
        else
        {
            char buff[33];
            memset(buff, ' ', 33);
            strncpy(buff, js_vessel->json + js_vessel->tokens[i].start, js_vessel->tokens[i].end - js_vessel->tokens[i].start);
            buff[32] = '\0';
            printf("%s |\n", buff);
        }
        printf("+-------+------------+------+--------+----------------------------------+\n");
    }
}

/*******************************************************************************
 * Function Name : print_token
 * Description   : print the token
 * Input         : [js_vessel_t *js_vessel, int token_id]
 *                 - js_vessel: target js_vessel
 *                 - token_id: the token id
 * Return        : NULL
 * Notes         : print the token
 *******************************************************************************/
void print_token(js_vessel_t *js_vessel, int token_id)
{
    // print the token content
    printf("+-------+------------+------+--------+----------------------------------+\n");
    printf("| tk_id |    type    | size | parent |              value               |\n");
    printf("+-------+------------+------+--------+----------------------------------+\n");
    // match the token type
    string type = get_json_token_type(js_vessel, token_id);

    printf("| %-5d | %s | %-4d |  %-4d  | ", token_id, type, js_vessel->tokens[token_id].size, js_vessel->tokens[token_id].parent);
    if (js_vessel->tokens[token_id].end - js_vessel->tokens[token_id].start > 32)
    {
        printf("%.*s%s |\n", 32 - 3, js_vessel->json + js_vessel->tokens[token_id].start, "...");
    }
    else
    {
        char buff[33];
        memset(buff, ' ', 33);
        strncpy(buff, js_vessel->json + js_vessel->tokens[token_id].start, js_vessel->tokens[token_id].end - js_vessel->tokens[token_id].start);
        buff[32] = '\0';
        printf("%s |\n", buff);
    }
    printf("+-------+------------+------+--------+----------------------------------+\n");
}

/*******************************************************************************
 * Function Name : print_json
 * Description   : print the json string
 * Input         : [js_vessel_t *js_vessel]
 *                 - js_vessel: target js_vessel
 * Return        : NULL
 * Notes         : print the json string
 *******************************************************************************/
void print_json(js_vessel_t *js_vessel)
{
    // check js_vessel
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        printf("js_vessel is invalid\n");
        return;
    }

    // print the json string
    printf("%s\n", js_vessel->json);
}

/*******************************************************************************
 * Function Name : get_json_token_count
 * Description   : get the json token count
 * Input         : [js_vessel_t *js_vessel]
 *                 - js_vessel: target js_vessel
 * Return        : the json token count
 * Notes         : none
 *******************************************************************************/
int get_json_token_count(js_vessel_t *js_vessel)
{
    // check js_vessel
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return 0;
    }

    // return the json token count
    return js_vessel->tokens_count;
}

/*******************************************************************************
 * Function Name : get_json_token_length
 * Description   : get the json token length
 * Input         : [js_vessel_t *js_vessel, int token_id]
 *                 - js_vessel: target js_vessel
 *                 - token_id: the token id
 * Return        : the json token length
 * Notes         : none
 *******************************************************************************/
int get_json_token_length(js_vessel_t *js_vessel, int token_id)
{
    // check js_vessel
    json_err_t err = check_json_vessel(js_vessel);
    if (err != JSON_OK)
    {
        return -1;
    }

    // check token_id
    if (token_id < 0 || token_id >= js_vessel->tokens_count)
    {
        return -1;
    }

    // return the json token length
    return js_vessel->tokens[token_id].end - js_vessel->tokens[token_id].start;
}

/*******************************************************************************
 * Function Name : check_json_vessel
 * Description   : check if the json vessel is valid
 * Input         : [js_vessel_t *js_vessel]
 *                 js_vessel: the json vessel
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_VESSLE_IS_NULL: the json vessel is null
 *                 JSON_ERR_VESSLE_TOKENS_IS_NULL: the json vessel's tokens is null
 *                 JSON_ERR_VESSLE_TOKENS_COUNT_ZERO: the json vessel's tokens count is zero
 *                 JSON_ERR_VESSLE_TOKENS_COUNT_NEGATIVE: the json vessel's tokens count is negative
 *                 JSON_ERR_VESSLE_JSON_IS_NULL: the json vessel's json string is null
 *                 JSON_ERR_VESSLE_JSON_IS_TOO_SHORT: the json vessel's json string is too short
 *                 JSON_ERR_VESSLE_JSON_IS_TOO_LONG: the json vessel's json string is too long
 * Notes         : this function is only for internal use
 *******************************************************************************/
json_err_t check_json_vessel(js_vessel_t *js_vessel)
{
    // check if the json vessel is null
    if (js_vessel == NULL)
    {
        return JSON_ERR_VESSLE_IS_NULL;
    }

    // check if the json vessel's tokens is null
    if (js_vessel->tokens == NULL)
    {
        return JSON_ERR_VESSLE_TOKENS_IS_NULL;
    }

    // check if the json vessel's tokens count is zero
    if (js_vessel->tokens_count == 0)
    {
        return JSON_ERR_VESSLE_TOKENS_COUNT_ZERO;
    }

    // check if the json vessel's tokens count is negative
    if (js_vessel->tokens_count < 0)
    {
        return JSON_ERR_VESSLE_TOKENS_COUNT_NEGATIVE;
    }

    // check if the json vessel's json string is null
    if (js_vessel->json == NULL)
    {
        return JSON_ERR_VESSLE_JSON_IS_NULL;
    }

    // check if the json vessel's json string is too short
    if (strlen(js_vessel->json) < 7)
    {
        return JSON_ERR_VESSLE_JSON_IS_TOO_SHORT;
    }

    // check if the json vessel's json string is too long
    if (strlen(js_vessel->json) > MAX_JSON_SIZE)
    {
        return JSON_ERR_VESSLE_JSON_IS_TOO_LONG;
    }

    // return success
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : free_json_vessel
 * Description   : free the json vessel
 * Input         : [js_vessel_t *js_vessel]
 *                 js_vessel: the json vessel
 * Return        : JSON_OK: success
 *                 JSON_ERR: error of unknown
 *                 JSON_ERR_VESSLE_IS_NULL: the json vessel is null
 * Notes         : none
 *******************************************************************************/
json_err_t free_json_vessel(js_vessel_t *js_vessel)
{
    // check if the json vessel is null
    if (js_vessel == NULL)
    {
        return JSON_ERR_VESSLE_IS_NULL;
    }

    // free the json vessel's tokens
    if (js_vessel->tokens)
    {
        free(js_vessel->tokens);
        js_vessel->tokens = NULL;
    }

    // free the json vessel's json string
    if (js_vessel->json)
    {
        free(js_vessel->json);
        js_vessel->json = NULL;
    }

    // free the json vessel
    // free(js_vessel);
    // js_vessel = NULL;

    // return success
    return JSON_OK;
}

/*******************************************************************************
 * Function Name : json_err_to_string
 * Description   : convert the json error to string
 * Input         : [json_err_t err]
 *                 err: the json error code
 * Return        : the json error string
 * Notes         : none
 *******************************************************************************/
string json_err_to_string(json_err_t err)
{
    switch (err)
    {
    case JSON_OK:
        return "JSON_OK";
    case JSON_ERR:
        return "JSON_ERR";
    case JSON_ERR_ALLOC_MEMORY_FAILED:
        return "JSON_ERR_ALLOC_MEMORY_FAILED";
    case JSON_ERR_ARRAY_INDEX_NOT_NUMBER:
        return "JSON_ERR_ARRAY_INDEX_NOT_NUMBER";
    case JSON_ERR_ARRAY_TOKEN_IS_OUT_OF_RANGE:
        return "JSON_ERR_ARRAY_TOKEN_IS_OUT_OF_RANGE";
    case JSON_ERR_ARRAY_TOKEN_IS_NOT_ARRAY:
        return "JSON_ERR_ARRAY_TOKEN_IS_NOT_ARRAY";
    case JSON_ERR_ARRAY_INDEX_IS_OUT_OF_RANGE:
        return "JSON_ERR_ARRAY_INDEX_IS_OUT_OF_RANGE";
    case JSON_ERR_ARRY_ITEM_NOT_FOUND:
        return "JSON_ERR_ARRY_ITEM_NOT_FOUND";
    case JSON_ERR_FILE_NOT_EXIST:
        return "JSON_ERR_FILE_NOT_EXIST";
    case JSON_ERR_JSON_PATH_IS_NULL:
        return "JSON_ERR_JSON_PATH_IS_NULL";
    case JSON_ERR_JSON_STRING_NULL:
        return "JSON_ERR_JSON_STRING_NULL";
    case JSON_ERR_JSON_STRING_TOO_SHORT:
        return "JSON_ERR_JSON_STRING_TOO_SHORT";
    case JSON_ERR_JSON_STRING_FORMAT_ERROR:
        return "JSON_ERR_JSON_STRING_FORMAT_ERROR";
    case JSON_ERR_JSON_STRING_NOT_COMPLETE:
        return "JSON_ERR_JSON_STRING_NOT_COMPLETE";
    case JSON_ERR_JSON_TOKENS_IS_TOO_LONG:
        return "JSON_ERR_JSON_TOKENS_IS_TOO_LONG";
    case JSON_ERR_KEY_PATH_IS_NULL:
        return "JSON_ERR_KEY_PATH_IS_NULL";
    case JSON_ERR_KEY_NOT_FOUND:
        return "JSON_ERR_KEY_NOT_FOUND";
    case JSON_ERR_KEY_NOT_NUMBER:
        return "JSON_ERR_KEY_NOT_NUMBER";
    case JSON_ERR_KEY_NOT_STRING:
        return "JSON_ERR_KEY_NOT_STRING";
    case JSON_ERR_KEY_NOT_PRIMITIVE:
        return "JSON_ERR_KEY_NOT_PRIMITIVE";
    case JSON_ERR_KEY_NOT_ARRAY:
        return "JSON_ERR_KEY_NOT_ARRAY";
    case JSON_ERR_KEY_PATH_INVALID_ARRAY_INDEX:
        return "JSON_ERR_KEY_PATH_INVALID_ARRAY_INDEX";
    case JSON_ERR_MAX_LEN_IS_NEGATIVE:
        return "JSON_ERR_MAX_LEN_IS_NEGATIVE";
    case JSON_ERR_OBJECT_TOO_LONG:
        return "JSON_ERR_OBJECT_TOO_LONG";
    case JSON_ERR_OUT_IS_NULL:
        return "JSON_ERR_OUT_IS_NULL";
    case JSON_ERR_TOKENID_IS_OUT_OF_RANGE:
        return "JSON_ERR_TOKENID_IS_OUT_OF_RANGE";
    case JSON_ERR_TOKENID_IS_NOT_OBJECT:
        return "JSON_ERR_TOKENID_IS_NOT_OBJECT";
    case JSON_ERR_VESSLE_IS_NULL:
        return "JSON_ERR_VESSLE_IS_NULL";
    case JSON_ERR_VESSLE_TOKENS_IS_NULL:
        return "JSON_ERR_VESSLE_TOKENS_IS_NULL";
    case JSON_ERR_VESSLE_TOKENS_COUNT_ZERO:
        return "JSON_ERR_VESSLE_TOKENS_COUNT_ZERO";
    case JSON_ERR_VESSLE_TOKENS_COUNT_NEGATIVE:
        return "JSON_ERR_VESSLE_TOKENS_COUNT_NEGATIVE";
    case JSON_ERR_VESSLE_JSON_IS_NULL:
        return "JSON_ERR_VESSLE_JSON_IS_NULL";
    case JSON_ERR_VESSLE_JSON_IS_TOO_SHORT:
        return "JSON_ERR_VESSLE_JSON_IS_TOO_SHORT";
    case JSON_ERR_VESSLE_JSON_IS_TOO_LONG:
        return "JSON_ERR_VESSLE_JSON_IS_TOO_LONG";
    }
    return "JSON_ERR_UNKNOWN";
}

/************************ (C) COPYRIGHT PURESUN **** END OF FILE **************/
